We will do an exercise using some more complicated documents but closer to real usage. There is a file containing restaurant information. An array of JSONs that can be easily ingested using mongolite’s import functionality.
library(mongolite)
r <- mongo(db = "test", collection = "restaurants")
r$import(file("restaurants.json"))
Done! How many documents are there?
r$count()
[1] 25359
That’s simple enough. Now what? We could try to launch some queries…
r$find(limit = 10)
But trying to infer the schema could be really tricky given that each document can be potentially different.
For that it is much easier to use MongoDB’s Compass tool that allows to explore the schema interactively.

From there it can be easily explored the information contained inside our collection, the percentage of data belonging to each data type inside a field or even the query to filter said information subset with a simple click.

Even if we have geospatial information we can get a map visualizing where each document information is geographically located.
Most of what we have done inside this notebook can be done using Compass in a more visual manner. Importing documents for example.

Or building aggregation pipelines.

Let’s do some simple exercises.
How many restaurants are located in Mahattan?
df <- r$find('')
nrow(df)
How many Chinese or Japanese restaurants are there? In Mahattan?
df <- r$find('')
nrow(df)
Any restaurant that has been closed lately?
df <- r$find('')
nrow(df)
How many restaurants are there per borough?
q <- '[
{"$group" : {"_id" : "", "numrestaurants" : {}}
]'
r$aggregate(q)
Show top 10 restaurants in Mahattan
We can also, for example, calculate the average score of the restaurants in Manhattan and sort them by grades in descending order, showing just the top 10.
q <- '[
{"$match" : {}},
{"$unwind" : ""},
{"$group" : {"_id" : "", "avggrade" : {}}},
{"$sort" : {}},
{"$limit" : 10}
]'
r$aggregate(q)
On indexes and efficiency
How efficient is that query? As we already discussed, the only indexed field is the _id field so when we condition that aggregation by borough it needs to scan the whole collection to get the right documents to process.

system.time(r$find('{"borough" : "Manhattan"}'))
user system elapsed
1.05 0.00 1.09
Not bad but we can do a little bit better thanks to indexes. We can create an index on that field using Compass or just by calling following command.
r$index(add = '{"borough" : 1}')

Thanks to that our query runs a little bit faster.
system.time(r$find('{"borough" : "Bronx"}'))
user system elapsed
0.23 0.02 0.27

Indexes can be really useful and being MongoDB an operational database for 21st century applications, text and geospatial searches are included. Imagine that you would like to complement the previous request as follows…
Find top 10 coffee houses near me
First, we will need to define what means near me. That is given my location and restaurant location I need to do a search for those that are at a given distance.
r$index(add = '{ "address.coord" : "2d" }')
With that index created we can look for documents with location field informed and close to a given location (mine).
r$find('{ "address.coord" : { "$geoWithin" : { "$center" : [ [-74, 40.74 ] , 0.01 ] } } }')
It certainly is more spectacular when used inside an application that shows it on a map.

Now, coffee houses… well anything with coffee on it. Text indexes will help us with that. We can look on a single field.
r$index(add = '{ "name" : "text" }')
Look in different fields.
r$index(add = '{ "name" : "text", "cuisine" : "text" }')
Or look on any text field using a wildcard.
r$index(add = '{ "$**" : "text"}')
It takes a while but thanks to this we can use the text and search operators and look for coffee houses.
r$find('{ "$text": { "$search": "cafe" } }')
As you see uppercase and lowercase now doesn’t matter that much. We can also negate some terms such as tea.
r$find('{ "$text": { "$search": "cafe -tea" } }')
So it should be easy now to at least filter the two requirements for our top 10 coffee houses nearby.
df <- r$find('{"$and" : [{ "address.coord" : { "$geoWithin" : { "$center" : [ [-74, 40.74 ] , 0.01 ] } } }, { "$text": { "$search": "cafe -tea" } }]}')
df
Not as impressive as in Compass :)

Application example
We could use also tools such as Leaflet to show our results on a map.
library(leaflet)
m <- leaflet() %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=-74, lat=40.74, popup="Here I am")
m
df <- r$find('{"$and" : [{ "address.coord" : { "$geoWithin" : { "$center" : [ [-74, 40.74 ] , 0.01 ] } } }, { "$text": { "$search": "cafe -tea" } }]}', fields = '{"name" : true, "_id" : 0, "address.coord" : true}')
icons <- awesomeIcons(
markerColor = "red"
)
for (row in 1:nrow(df)) {
name <- df[row, "name"]
coord <- df[row, "address"]
lat = coord[["coord"]][[1]][1]
lon = coord[["coord"]][[1]][2]
m <- m %>% addAwesomeMarkers(lat, lon, icon = icons, popup = name)
}
m
find <- function(me, search_text){
query <- '{"$and" : [{ "address.coord" : { "$geoWithin" : { "$center" : [ ['
query <- paste0(query, me[1])
query <- paste0(query, ",")
query <- paste0(query, me[2])
query <- paste0(query, '] , 0.01 ] } } }, { "$text": {"$search": "')
query <- paste0(query, search_text)
query <- paste0(query, '" } }]}')
df <- r$find(query, fields = '{"name" : true, "_id" : 0, "address.coord" : true}')
icons <- awesomeIcons(
markerColor = "red"
)
m <- leaflet() %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=me[1], lat=me[2], popup="Here I am")
for (row in 1:nrow(df)) {
name <- df[row, "name"]
coord <- df[row, "address"]
lat = coord[["coord"]][[1]][1]
lon = coord[["coord"]][[1]][2]
m <- m %>% addAwesomeMarkers(lat, lon, icon = icons, popup = name)
}
return(m)
}
find(c(-74, 40.74), "chinese")
find(c(-74, 40.74), "kosher")
Lesson number 4: Indexes are cool!
LS0tDQp0aXRsZTogIlJlc3RhdXJhbnRzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KV2Ugd2lsbCBkbyBhbiBleGVyY2lzZSB1c2luZyBzb21lIG1vcmUgY29tcGxpY2F0ZWQgZG9jdW1lbnRzIGJ1dCBjbG9zZXIgdG8gcmVhbCB1c2FnZS4gVGhlcmUgaXMgYSBmaWxlIGNvbnRhaW5pbmcgcmVzdGF1cmFudCBpbmZvcm1hdGlvbi4gQW4gYXJyYXkgb2YgSlNPTnMgdGhhdCBjYW4gYmUgZWFzaWx5IGluZ2VzdGVkIHVzaW5nIG1vbmdvbGl0ZSdzIGltcG9ydCBmdW5jdGlvbmFsaXR5Lg0KDQpgYGB7cn0NCmxpYnJhcnkobW9uZ29saXRlKQ0KciA8LSBtb25nbyhkYiA9ICJ0ZXN0IiwgY29sbGVjdGlvbiA9ICJyZXN0YXVyYW50cyIpDQpyJGltcG9ydChmaWxlKCJyZXN0YXVyYW50cy5qc29uIikpDQpgYGANCg0KRG9uZSEgSG93IG1hbnkgZG9jdW1lbnRzIGFyZSB0aGVyZT8NCg0KYGBge3J9DQpyJGNvdW50KCkNCmBgYA0KDQpUaGF0J3Mgc2ltcGxlIGVub3VnaC4gTm93IHdoYXQ/IFdlIGNvdWxkIHRyeSB0byBsYXVuY2ggc29tZSBxdWVyaWVzLi4uDQoNCmBgYHtyfQ0KciRmaW5kKGxpbWl0ID0gMTApDQpgYGANCg0KQnV0IHRyeWluZyB0byBpbmZlciB0aGUgc2NoZW1hIGNvdWxkIGJlIHJlYWxseSB0cmlja3kgZ2l2ZW4gdGhhdCBlYWNoIGRvY3VtZW50IGNhbiBiZSBwb3RlbnRpYWxseSBkaWZmZXJlbnQuIA0KDQpGb3IgdGhhdCBpdCBpcyBtdWNoIGVhc2llciB0byB1c2UgTW9uZ29EQidzIFtDb21wYXNzXShodHRwczovL3d3dy5tb25nb2RiLmNvbS9wcm9kdWN0cy9jb21wYXNzKSB0b29sIHRoYXQgYWxsb3dzIHRvIGV4cGxvcmUgdGhlIHNjaGVtYSBpbnRlcmFjdGl2ZWx5Lg0KDQohW10oaW1hZ2VzL3NjaGVtYS5wbmcpDQoNCkZyb20gdGhlcmUgaXQgY2FuIGJlIGVhc2lseSBleHBsb3JlZCB0aGUgaW5mb3JtYXRpb24gY29udGFpbmVkIGluc2lkZSBvdXIgY29sbGVjdGlvbiwgdGhlIHBlcmNlbnRhZ2Ugb2YgZGF0YSBiZWxvbmdpbmcgdG8gZWFjaCBkYXRhIHR5cGUgaW5zaWRlIGEgZmllbGQgb3IgZXZlbiB0aGUgcXVlcnkgdG8gZmlsdGVyIHNhaWQgaW5mb3JtYXRpb24gc3Vic2V0IHdpdGggYSBzaW1wbGUgY2xpY2suDQoNCiFbXShpbWFnZXMvc2NoZW1hMi5wbmcpDQoNCkV2ZW4gaWYgd2UgaGF2ZSBnZW9zcGF0aWFsIGluZm9ybWF0aW9uIHdlIGNhbiBnZXQgYSBtYXAgdmlzdWFsaXppbmcgd2hlcmUgZWFjaCBkb2N1bWVudCBpbmZvcm1hdGlvbiBpcyBnZW9ncmFwaGljYWxseSBsb2NhdGVkLg0KDQohW10oaW1hZ2VzL3NjaGVtYTMucG5nKQ0KTW9zdCBvZiB3aGF0IHdlIGhhdmUgZG9uZSBpbnNpZGUgdGhpcyBub3RlYm9vayBjYW4gYmUgZG9uZSB1c2luZyBDb21wYXNzIGluIGEgbW9yZSB2aXN1YWwgbWFubmVyLiANCkltcG9ydGluZyBkb2N1bWVudHMgZm9yIGV4YW1wbGUuDQoNCiFbXShpbWFnZXMvaW1wb3J0LnBuZykNCg0KT3IgYnVpbGRpbmcgYWdncmVnYXRpb24gcGlwZWxpbmVzLg0KDQohW10oaW1hZ2VzL2FnZ3JlZ2F0aW9uLnBuZykNCg0KTGV0J3MgZG8gc29tZSBzaW1wbGUgZXhlcmNpc2VzLg0KDQojIyMgSG93IG1hbnkgcmVzdGF1cmFudHMgYXJlIGxvY2F0ZWQgaW4gTWFoYXR0YW4/DQoNCmBgYHtyfQ0KZGYgPC0gciRmaW5kKCcnKQ0KbnJvdyhkZikNCmBgYA0KDQojIyMgSG93IG1hbnkgQ2hpbmVzZSBvciBKYXBhbmVzZSByZXN0YXVyYW50cyBhcmUgdGhlcmU/IEluIE1haGF0dGFuPw0KDQpgYGB7cn0NCmRmIDwtIHIkZmluZCgnJykNCm5yb3coZGYpDQpgYGANCg0KIyMjIEFueSByZXN0YXVyYW50IHRoYXQgaGFzIGJlZW4gY2xvc2VkIGxhdGVseT8NCg0KYGBge3J9DQpkZiA8LSByJGZpbmQoJycpDQpucm93KGRmKQ0KYGBgDQoNCiMjIyBIb3cgbWFueSByZXN0YXVyYW50cyBhcmUgdGhlcmUgcGVyIGJvcm91Z2g/DQoNCmBgYHtyfQ0KcSA8LSAnWw0KICAgICAgICAgICAgeyIkZ3JvdXAiIDogeyJfaWQiIDogIiIsICJudW1yZXN0YXVyYW50cyIgOiB7fX0NCl0nDQpyJGFnZ3JlZ2F0ZShxKQ0KYGBgDQoNCiMjIyBTaG93IHRvcCAxMCByZXN0YXVyYW50cyBpbiBNYWhhdHRhbg0KDQpXZSBjYW4gYWxzbywgZm9yIGV4YW1wbGUsIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBzY29yZSBvZiB0aGUgcmVzdGF1cmFudHMgaW4gTWFuaGF0dGFuIGFuZCBzb3J0IHRoZW0gYnkgZ3JhZGVzIGluIGRlc2NlbmRpbmcgb3JkZXIsIHNob3dpbmcganVzdCB0aGUgdG9wIDEwLg0KDQpgYGB7cn0NCnEgPC0gJ1sNCiAgICAgICAgICAgIHsiJG1hdGNoIiA6IHt9fSwNCiAgICAgICAgICAgIHsiJHVud2luZCIgOiAiIn0sDQogICAgICAgICAgICB7IiRncm91cCIgOiB7Il9pZCIgOiAiIiwgImF2Z2dyYWRlIiA6IHt9fX0sDQogICAgICAgICAgICB7IiRzb3J0IiA6IHt9fSwNCiAgICAgICAgICAgIHsiJGxpbWl0IiA6IDEwfQ0KXScNCnIkYWdncmVnYXRlKHEpDQpgYGANCg0KIyBPbiBpbmRleGVzIGFuZCBlZmZpY2llbmN5DQoNCkhvdyBlZmZpY2llbnQgaXMgdGhhdCBxdWVyeT8gQXMgd2UgYWxyZWFkeSBkaXNjdXNzZWQsIHRoZSBvbmx5IGluZGV4ZWQgZmllbGQgaXMgdGhlIF9pZCBmaWVsZCBzbyB3aGVuIHdlIGNvbmRpdGlvbiB0aGF0IGFnZ3JlZ2F0aW9uIGJ5ICpib3JvdWdoKiBpdCBuZWVkcyB0byBzY2FuIHRoZSB3aG9sZSBjb2xsZWN0aW9uIHRvIGdldCB0aGUgcmlnaHQgZG9jdW1lbnRzIHRvIHByb2Nlc3MuDQoNCiFbXShpbWFnZXMvZXhwbGFpbi5wbmcpDQoNCg0KYGBge3J9DQpzeXN0ZW0udGltZShyJGZpbmQoJ3siYm9yb3VnaCIgOiAiTWFuaGF0dGFuIn0nKSkNCmBgYA0KDQpOb3QgYmFkIGJ1dCB3ZSBjYW4gZG8gYSBsaXR0bGUgYml0IGJldHRlciB0aGFua3MgdG8gaW5kZXhlcy4gV2UgY2FuIGNyZWF0ZSBhbiBpbmRleCBvbiB0aGF0IGZpZWxkIHVzaW5nIENvbXBhc3Mgb3IganVzdCBieSBjYWxsaW5nIGZvbGxvd2luZyBjb21tYW5kLg0KDQpgYGB7cn0NCnIkaW5kZXgoYWRkID0gJ3siYm9yb3VnaCIgOiAxfScpDQpgYGANCiFbXShpbWFnZXMvaW5kZXgucG5nKQ0KDQpUaGFua3MgdG8gdGhhdCBvdXIgcXVlcnkgcnVucyBhIGxpdHRsZSBiaXQgZmFzdGVyLg0KDQpgYGB7cn0NCnN5c3RlbS50aW1lKHIkZmluZCgneyJib3JvdWdoIiA6ICJNYW5oYXR0YW4ifScpKQ0KYGBgDQohW10oaW1hZ2VzL2V4cGxhaW5pZHgucG5nKQ0KDQpJbmRleGVzIGNhbiBiZSByZWFsbHkgdXNlZnVsIGFuZCBiZWluZyBNb25nb0RCIGFuIG9wZXJhdGlvbmFsIGRhdGFiYXNlIGZvciAyMXN0IGNlbnR1cnkgYXBwbGljYXRpb25zLCB0ZXh0IGFuZCBnZW9zcGF0aWFsIHNlYXJjaGVzIGFyZSBpbmNsdWRlZC4gSW1hZ2luZSB0aGF0IHlvdSB3b3VsZCBsaWtlIHRvIGNvbXBsZW1lbnQgdGhlIHByZXZpb3VzIHJlcXVlc3QgYXMgZm9sbG93cy4uLg0KDQojIEZpbmQgdG9wIDEwIGNvZmZlZSBob3VzZXMgbmVhciBtZQ0KDQpGaXJzdCwgd2Ugd2lsbCBuZWVkIHRvIGRlZmluZSB3aGF0IG1lYW5zICpuZWFyIG1lKi4gVGhhdCBpcyBnaXZlbiBteSBsb2NhdGlvbiBhbmQgcmVzdGF1cmFudCBsb2NhdGlvbiBJIG5lZWQgdG8gZG8gYSBzZWFyY2ggZm9yIHRob3NlIHRoYXQgYXJlIGF0IGEgZ2l2ZW4gZGlzdGFuY2UuDQoNCmBgYHtyfQ0KciRpbmRleChhZGQgPSAneyAiYWRkcmVzcy5jb29yZCIgOiAiMmQiIH0nKQ0KYGBgDQoNCldpdGggdGhhdCBpbmRleCBjcmVhdGVkIHdlIGNhbiBsb29rIGZvciBkb2N1bWVudHMgd2l0aCBsb2NhdGlvbiBmaWVsZCBpbmZvcm1lZCBhbmQgY2xvc2UgdG8gYSBnaXZlbiBsb2NhdGlvbiAobWluZSkuDQoNCmBgYHtyfQ0KciRmaW5kKCd7ICJhZGRyZXNzLmNvb3JkIiA6IHsgIiRnZW9XaXRoaW4iIDogeyAiJGNlbnRlciIgOiBbIFstNzQsIDQwLjc0IF0gLCAwLjAxIF0gfSB9IH0nKQ0KYGBgDQoNCkl0IGNlcnRhaW5seSBpcyBtb3JlIHNwZWN0YWN1bGFyIHdoZW4gdXNlZCBpbnNpZGUgYW4gYXBwbGljYXRpb24gdGhhdCBzaG93cyBpdCBvbiBhIG1hcC4NCg0KIVtdKGltYWdlcy9nZW8ucG5nKQ0KDQpOb3csIGNvZmZlZSBob3VzZXMuLi4gd2VsbCBhbnl0aGluZyB3aXRoICpjb2ZmZWUqIG9uIGl0LiBUZXh0IGluZGV4ZXMgd2lsbCBoZWxwIHVzIHdpdGggdGhhdC4gV2UgY2FuIGxvb2sgb24gYSBzaW5nbGUgZmllbGQuDQoNCmBgYG1vbmdvZGINCnIkaW5kZXgoYWRkID0gJ3sgIm5hbWUiIDogInRleHQiIH0nKQ0KYGBgDQoNCkxvb2sgaW4gZGlmZmVyZW50IGZpZWxkcy4NCg0KYGBgbW9uZ29kYg0KciRpbmRleChhZGQgPSAneyAibmFtZSIgOiAidGV4dCIsICJjdWlzaW5lIiA6ICJ0ZXh0IiB9JykNCmBgYA0KDQpPciBsb29rIG9uIGFueSB0ZXh0IGZpZWxkIHVzaW5nIGEgd2lsZGNhcmQuDQoNCmBgYHtyfQ0KciRpbmRleChhZGQgPSAneyAiJCoqIiA6ICJ0ZXh0In0nKQ0KYGBgDQoNCkl0IHRha2VzIGEgd2hpbGUgYnV0IHRoYW5rcyB0byB0aGlzIHdlIGNhbiB1c2UgdGhlICp0ZXh0KiBhbmQgKnNlYXJjaCogb3BlcmF0b3JzIGFuZCBsb29rIGZvciBjb2ZmZWUgaG91c2VzLg0KDQpgYGB7cn0NCnIkZmluZCgneyAiJHRleHQiOiB7ICIkc2VhcmNoIjogImNhZmUiIH0gfScpDQpgYGANCg0KQXMgeW91IHNlZSB1cHBlcmNhc2UgYW5kIGxvd2VyY2FzZSBub3cgZG9lc24ndCBtYXR0ZXIgdGhhdCBtdWNoLiBXZSBjYW4gYWxzbyBuZWdhdGUgc29tZSB0ZXJtcyBzdWNoIGFzIHRlYS4NCg0KYGBge3J9DQpyJGZpbmQoJ3sgIiR0ZXh0IjogeyAiJHNlYXJjaCI6ICJjYWZlIC10ZWEiIH0gfScpDQpgYGANCg0KU28gaXQgc2hvdWxkIGJlIGVhc3kgbm93IHRvIGF0IGxlYXN0IGZpbHRlciB0aGUgdHdvIHJlcXVpcmVtZW50cyBmb3Igb3VyIHRvcCAxMCBjb2ZmZWUgaG91c2VzIG5lYXJieS4NCg0KYGBge3J9DQpyJGZpbmQoJ3siJGFuZCIgOiBbeyAiYWRkcmVzcy5jb29yZCIgOiB7ICIkZ2VvV2l0aGluIiA6IHsgIiRjZW50ZXIiIDogWyBbLTc0LCA0MC43NCBdICwgMC4wMSBdIH0gfSB9LCB7ICIkdGV4dCI6IHsgIiRzZWFyY2giOiAiY2FmZSAtdGVhIiB9IH1dfScpDQpgYGANCg0KTm90IGFzIGltcHJlc3NpdmUgYXMgaW4gQ29tcGFzcyA6KQ0KDQohW10oaW1hZ2VzL2Nsb3NlY29mZmVlLnBuZykNCg0KIyBBcHBsaWNhdGlvbiBleGFtcGxlDQoNCldlIGNvdWxkIHVzZSBhbHNvIHRvb2xzIHN1Y2ggYXMgTGVhZmxldCB0byBzaG93IG91ciByZXN1bHRzIG9uIGEgbWFwLg0KDQpgYGB7cn0NCmxpYnJhcnkobGVhZmxldCkNCg0KbSA8LSBsZWFmbGV0KCkgJT4lDQogIGFkZFRpbGVzKCkgJT4lICAjIEFkZCBkZWZhdWx0IE9wZW5TdHJlZXRNYXAgbWFwIHRpbGVzDQogIGFkZE1hcmtlcnMobG5nPS03NCwgbGF0PTQwLjc0LCBwb3B1cD0iSGVyZSBJIGFtIikNCm0NCmBgYA0KYGBge3J9DQpkZiA8LSByJGZpbmQoJ3siJGFuZCIgOiBbeyAiYWRkcmVzcy5jb29yZCIgOiB7ICIkZ2VvV2l0aGluIiA6IHsgIiRjZW50ZXIiIDogWyBbLTc0LCA0MC43NCBdICwgMC4wMSBdIH0gfSB9LCB7ICIkdGV4dCI6IHsgIiRzZWFyY2giOiAiY2FmZSAtdGVhIiB9IH1dfScsIGZpZWxkcyA9ICd7Im5hbWUiIDogdHJ1ZSwgIl9pZCIgOiAwLCAiYWRkcmVzcy5jb29yZCIgOiB0cnVlfScpDQoNCmljb25zIDwtIGF3ZXNvbWVJY29ucygNCiAgbWFya2VyQ29sb3IgPSAicmVkIg0KKQ0KDQpmb3IgKHJvdyBpbiAxOm5yb3coZGYpKSB7DQogICAgbmFtZSA8LSBkZltyb3csICJuYW1lIl0NCiAgICBjb29yZCA8LSBkZltyb3csICJhZGRyZXNzIl0NCiAgICBsYXQgPSBjb29yZFtbImNvb3JkIl1dW1sxXV1bMV0NCiAgICBsb24gPSBjb29yZFtbImNvb3JkIl1dW1sxXV1bMl0NCg0KICAgIG0gPC0gbSAlPiUgYWRkQXdlc29tZU1hcmtlcnMobGF0LCBsb24sIGljb24gPSBpY29ucywgcG9wdXAgPSBuYW1lKQ0KfQ0KbQ0KYGBgDQoNCmBgYHtyfQ0KDQpmaW5kIDwtIGZ1bmN0aW9uKG1lLCBzZWFyY2hfdGV4dCl7DQogIHF1ZXJ5IDwtICd7IiRhbmQiIDogW3sgImFkZHJlc3MuY29vcmQiIDogeyAiJGdlb1dpdGhpbiIgOiB7ICIkY2VudGVyIiA6IFsgWycNCiAgcXVlcnkgPC0gcGFzdGUwKHF1ZXJ5LCBtZVsxXSkNCiAgcXVlcnkgPC0gcGFzdGUwKHF1ZXJ5LCAiLCIpDQogIHF1ZXJ5IDwtIHBhc3RlMChxdWVyeSwgbWVbMl0pDQogIHF1ZXJ5IDwtIHBhc3RlMChxdWVyeSwgJ10gLCAwLjAxIF0gfSB9IH0sIHsgIiR0ZXh0IjogeyIkc2VhcmNoIjogIicpDQogIHF1ZXJ5IDwtIHBhc3RlMChxdWVyeSwgc2VhcmNoX3RleHQpDQogIHF1ZXJ5IDwtIHBhc3RlMChxdWVyeSwgJyIgfSB9XX0nKQ0KICBkZiA8LSByJGZpbmQocXVlcnksIGZpZWxkcyA9ICd7Im5hbWUiIDogdHJ1ZSwgIl9pZCIgOiAwLCAiYWRkcmVzcy5jb29yZCIgOiB0cnVlfScpDQogIA0KICBpY29ucyA8LSBhd2Vzb21lSWNvbnMoDQogICAgbWFya2VyQ29sb3IgPSAicmVkIg0KICApDQogIA0KICBtIDwtIGxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUgICMgQWRkIGRlZmF1bHQgT3BlblN0cmVldE1hcCBtYXAgdGlsZXMNCiAgYWRkTWFya2Vycyhsbmc9bWVbMV0sIGxhdD1tZVsyXSwgcG9wdXA9IkhlcmUgSSBhbSIpDQogIA0KICBmb3IgKHJvdyBpbiAxOm5yb3coZGYpKSB7DQogICAgICBuYW1lIDwtIGRmW3JvdywgIm5hbWUiXQ0KICAgICAgY29vcmQgPC0gZGZbcm93LCAiYWRkcmVzcyJdDQogICAgICBsYXQgPSBjb29yZFtbImNvb3JkIl1dW1sxXV1bMV0NCiAgICAgIGxvbiA9IGNvb3JkW1siY29vcmQiXV1bWzFdXVsyXQ0KICANCiAgICAgIG0gPC0gbSAlPiUgYWRkQXdlc29tZU1hcmtlcnMobGF0LCBsb24sIGljb24gPSBpY29ucywgcG9wdXAgPSBuYW1lKQ0KICB9DQogIA0KICByZXR1cm4obSkNCn0NCmBgYA0KDQpgYGB7cn0NCmZpbmQoYygtNzQsIDQwLjc0KSwgImNoaW5lc2UiKQ0KYGBgDQpgYGB7cn0NCmZpbmQoYygtNzQsIDQwLjc0KSwgImtvc2hlciIpDQpgYGANCg0KDQoqKkxlc3NvbiBudW1iZXIgNDogSW5kZXhlcyBhcmUgY29vbCEqKg0KDQo=